#include "monteCarlo.h"
#include "utils.h"

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>

//-----------------------------------------------
// Sequencial
//-----------------------------------------------

void monteCarlo_sequencial (long int n) {

    long int inside = 0, i;
    double r1, r2;

    #ifdef CALCULATE_TIME
    double time = clock();
    #endif

    unsigned long rand_state = clock();

    // Gerar pontos e guardar numero de pontos dentro do circulo
    for (i = 0; i < n; ++i)
    {
        r1 = random0_1(&rand_state);
        r2 = random0_1(&rand_state);

        if (r1*r1 + r2*r2 <= 1.0)
            ++inside;
    }

    #ifdef CALCULATE_TIME
    time = (clock() - time) / CLOCKS_PER_SEC;
    #endif

    printf("%lf\n", 4.0*inside/(double)n);

    #ifdef CALCULATE_TIME
    printf("Time: %lfs\n", time);
    #endif
}

//-----------------------------------------------
// Paralelo
//-----------------------------------------------

struct monteCarloData {
    long int a, result;
};

void * monteCarlo_thread (void* data) {
    char b = 0;
    long int i;
    double r1, r2;
    struct monteCarloData *x = (struct monteCarloData*)data;

    if (x->result == 0)
        b = 1;

    x->result = 0;

    // Criar um estado aleatorio para cada thread
    unsigned long rand_state = clock() + rand();

    // Gerar pontos e guardar numero de pontos dentro do circulo
    for (i = 0; i < x->a; ++i)
    {
        r1 = random0_1(&rand_state);
        r2 = random0_1(&rand_state);

        if (r1*r1 + r2*r2 <= 1.0)
            ++(x->result);
    }

    // Nao chamar pthread_exit para a thread principal
    if (b)
        pthread_exit(NULL);

    return NULL;
}

void monteCarlo_paralelo (long int n, int threads) {

    int err, t;
    pthread_t *threads_v;
    long int intervalo, sum=0;
    struct monteCarloData *p_mcd;

    p_mcd = (struct monteCarloData*) malloc(sizeof(struct monteCarloData)*threads);
    threads_v = (pthread_t*) malloc(sizeof(pthread_t)*threads);

    #ifdef CALCULATE_TIME
    double time = clock();
    #endif

    intervalo = n/threads;

    // Criar threads
    for(t = 0; t < threads-1; t++) {

        p_mcd[t].a = intervalo;
        p_mcd[t].result = 0;

        err = pthread_create(&threads_v[t], NULL, monteCarlo_thread, (void *) &p_mcd[t]);

        if (err) {
            printf("ERRO: %d\n", err);
            exit(-1);
        }
    }

    // Executar na thread principal
    p_mcd[t].a = intervalo + n%threads;
    p_mcd[t].result = -1; // Evitar que chame pthread_exit na funcao

    monteCarlo_thread ((void*)&p_mcd[t]);
    sum += p_mcd[t].result;

    // Acumular soma com valor das outras threads
    for(t = 0; t < threads-1; t++) {
        pthread_join (threads_v[t], NULL);
        sum += p_mcd[t].result;
    }

    #ifdef CALCULATE_TIME
    time = (clock() - time) / CLOCKS_PER_SEC;
    #endif

    free (p_mcd);
    free (threads_v);

    printf("%lf\n", 4.0*sum/(double)n);

    #ifdef CALCULATE_TIME
    printf("Time: %lfs\n", time);
    #endif
}
